/**
 * Copyright (c) 2011 eelpo.com
 * All rights reserved.
 */
package com.eelpo.common.security;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;

import com.eelpo.common.encoder.BASE64DecoderStream;
import com.eelpo.common.encoder.BASE64EncoderStream;

/**
 * 功能描述：数字签名.使用签名算法SHA1withRSA,MD2withRSA,MD5withRSA.
 * 私钥签名,公钥验证。信息摘要的哈希算法推荐使用SHA-1,MD5
 * <p>
 * 版权所有：广州市艾尔博科技有限公司
 * <p>
 * 未经本公司许可，不得以任何方式复制或使用本程序任何部分
 * 
 * @author <a href="mailto:Brian@eelpo.com">汪洪州</a>
 * @since 1.0
 */
public class DigitalSignature {

	// public static final Integer RSA_KEYSIZE = 1024;
	// public static final String RSA_ALGORITHM = "RSA";
	// public static final String SHA1WITHRSA = "SHA1withRSA";
	// public static final String MD2WITHRSA = "MD2withRSA";
	// public static final String MD5WITHRSA = "MD5withRSA";
	// public static final String MD5_ALGORITHM = "MD5";
	// public static final String SHA_ALGORITHM = "SHA";
	// public static final String SHA1_ALGORITHM = "SHA1";
	// public static final String SHA_1_ALGORITHM = "SHA-1";

	private String signatureAlgorithm;

	private String hashAlgorithm;

	public DigitalSignature() {

	}

	public DigitalSignature(String signatureAlgorithm, String hashAlgorithm) {

		this.signatureAlgorithm = signatureAlgorithm;
		this.hashAlgorithm = hashAlgorithm;

	}

	public String getHashAlgorithm() {
		return hashAlgorithm;
	}

	public void setHashAlgorithm(String hashAlgorithm) {
		this.hashAlgorithm = hashAlgorithm;
	}

	public String getSignatureAlgorithm() {
		return signatureAlgorithm;
	}

	public void setSignatureAlgorithm(String signatureAlgorithm) {
		this.signatureAlgorithm = signatureAlgorithm;
	}

	/**
	 * <p>
	 * 方法用途和描述：对字符串摘要进行签名
	 * </p>
	 * 
	 * @param privateKey
	 *            ：私钥对象
	 * @param plainText
	 *            ：要签名的字符串
	 * @param signatureAlgorithm
	 *            ：签名算法
	 * @param hashAlgorithm
	 *            ：信息摘要算法
	 * @return 返回信息摘要的签名
	 */
	public String signature(PrivateKey privateKey, String plainText) {

		// 对字符串签名
		byte[] cipherBytes = signature(privateKey, plainText.getBytes());

		// 使用64位加码器加码返回
		return BASE64EncoderStream.encode(cipherBytes);

	}

	/**
	 * <p>
	 * 方法用途和描述：对字节数组摘要进行签名
	 * </p>
	 * 
	 * @param privateKey
	 *            ：私钥对象
	 * @param plainBytes
	 *            ：要签名的字节数组
	 * @param signatureAlgorithm
	 *            ：签名算法
	 * @param hashAlgorithm
	 *            ：信息摘要算法
	 * @return 返回信息摘要的签名
	 */
	public byte[] signature(PrivateKey privateKey, byte[] plainBytes) {

		try {

			// 用哈希算法计算信息摘要
			byte[] digest = MessageDigestor.digest(plainBytes, hashAlgorithm);

			// 使用私钥对信息摘要进行签名
			Signature signature = Signature.getInstance(signatureAlgorithm);
			signature.initSign(privateKey);
			signature.update(digest);

			return signature.sign();

		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}

	}

	/**
	 * <p>
	 * 方法用途和描述：对文件摘要进行签名
	 * </p>
	 * 
	 * @param privateKey
	 *            ：私钥对象
	 * @param inputPath
	 *            ：要签名的文件路径
	 * @param outputPath
	 *            ：信心摘要文件路径
	 * @param signatureAlgorithm
	 *            ：签名算法
	 * @param hashAlgorithm
	 *            ：信息摘要算法
	 */
	public void signature(PrivateKey privateKey, String inputPath, String outputPath) {

		try {

			OutputStream outputStream = new FileOutputStream(outputPath);

			byte[] cipherBytes = signature(privateKey, getBytes(inputPath));

			outputStream.write(cipherBytes);

			outputStream.close();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * <p>
	 * 方法用途和描述：校验字符串是否改变
	 * </p>
	 * 
	 * @param publicKey
	 *            ：公钥对象
	 * @param plainText
	 *            ：要校验的字符串
	 * @param cipherText
	 *            ：字符串的信息摘要
	 * @param signatureAlgorithm
	 *            ：签名算法
	 * @param hashAlgorithm
	 *            ：信息摘要算法
	 * @return 返回校验结果
	 */
	public boolean verify(PublicKey publicKey, String plainText, String cipherText) {

		// 使用64位解码器解码校验
		return verify(publicKey, plainText.getBytes(), BASE64DecoderStream.decode(cipherText));
	}

	/**
	 * <p>
	 * 方法用途和描述：校验字节数组是否改变
	 * </p>
	 * 
	 * @param publicKey
	 *            ：公钥对象
	 * @param plainBytes
	 *            ：要校验的字字节数组
	 * @param cipherBytes
	 *            ：字节数组的信息摘要
	 * @param signatureAlgorithm
	 *            ：签名算法
	 * @param hashAlgorithm
	 *            ：信息摘要算法
	 * @return 返回校验结果
	 */
	public boolean verify(PublicKey publicKey, byte[] plainBytes, byte[] cipherBytes) {

		try {

			// 对要校验的数据使用哈希算法计算信息摘要
			byte[] digest = MessageDigestor.digest(plainBytes, hashAlgorithm);

			// 校验信息摘要是否相等
			Signature signature = Signature.getInstance(signatureAlgorithm);
			signature.initVerify(publicKey);
			signature.update(digest);

			return signature.verify(cipherBytes);

		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}

	}

	/**
	 * <p>
	 * 方法用途和描述：校验文件是否改变
	 * </p>
	 * 
	 * @param digest
	 *            ：信息摘要文件路径
	 * @param publicKey
	 *            ：公钥对象
	 * @param inputPath
	 *            ：要校验的文件路径
	 * @param signatureAlgorithm
	 *            ：签名算法
	 * @param hashAlgorithm
	 *            ：信息摘要算法
	 * @return 返回校验结果
	 */
	public boolean verify(String digestPath, PublicKey publicKey, String inputPath) {

		return verify(publicKey, getBytes(inputPath), getBytes(digestPath));

	}

	/*
	 * <p> 方法用途和描述：将文件转换成字节数组的形式 </p> @param input：需要转换的文件
	 * 
	 * @param inputPath：要转换的文件路径
	 * 
	 * @return 返回文件的字节数组形式
	 */
	private byte[] getBytes(String inputPath) {

		try {

			InputStream inputStream = new FileInputStream(inputPath);
			ByteArrayOutputStream byteStream = new ByteArrayOutputStream();

			for (int i = inputStream.read(); i != -1; i = inputStream.read())
				byteStream.write(i);

			byte[] bytes = byteStream.toByteArray();

			inputStream.close();
			byteStream.close();

			return bytes;

		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}

	}
}
